#include "ws2812b.h"

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <stdint.h>
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>

#include <cstdint>
#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <cerrno>
#include <byteswap.h>
#include <sstream>
#include <endian.h> //字节序
#include <algorithm>

#include <sys/types.h>

#include <signal.h>
#include <time.h>
#include <math.h>
#include <iostream>

namespace ws2812b
{
    //
    // Raspberry Pi Hardware Query 树莓派硬件查询功能
    //
#define LINE_WIDTH_MAX 80
#define HW_VER_STRING "Revision"

#define PERIPH_BASE_RPI 0x20000000
#define PERIPH_BASE_RPI2 0x3f000000
#define PERIPH_BASE_RPI4 0xfe000000

#define VIDEOCORE_BASE_RPI 0x40000000
#define VIDEOCORE_BASE_RPI2 0xc0000000

#define RPI_MANUFACTURER_MASK (0xf << 16)
#define RPI_WARRANTY_MASK (0x3 << 24)

    static const rpi_hw_t rpi_hw_info[] = {
        //
        // Raspberry Pi 400
        //
        {0xc03130, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 400 - 4GB v1.0"},
        {0xc03131, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 400 - 4GB v1.1"},
        //
        // Raspberry Pi 4
        //
        {0xA03111, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 1GB v1.1"},
        {0xB03111, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 2GB v.1.1"},
        {0xC03111, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 4GB v1.1"},
        {0xA03112, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 1GB v1.2"},
        {0xB03112, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 2GB v.1.2"},
        {0xC03112, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 4GB v1.2"},
        {0xb03114, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 2GB v1.4"},
        {0xD03114, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 8GB v1.4"},
        {0xc03114, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 4GB v1.4"},
        {0xa03115, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 1GB v1.5"},
        {0xb03115, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 2GB v1.5"},
        {0xc03115, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 4GB v1.5"},
        {0xd03115, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Pi 4 Model B - 8GB v1.5"},

        //
        // Compute Module 4
        //
        {0xa03140, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Compute Module 4 v1.0 eMMC"},
        {0xb03140, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Compute Module 4 v1.0 Lite"},
        {0xc03140, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Compute Module 4 v1.0 WiFi"},
        {0xd03140, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Compute Module 4 v1.0 WiFi 8GB"},
        {0xa03141, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Compute Module 4 Rev 1.1"},
        {0xb03141, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Compute Module 4 Rev 1.1"},
        {0xc03141, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Compute Module 4 Rev 1.1"},
        {0xd03141, RPI_HWVER_TYPE_PI4, PERIPH_BASE_RPI4, VIDEOCORE_BASE_RPI2, "Compute Module 4 Rev 1.1"},

        //
        // Model B Rev 1.0
        //
        {0x02, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B"},
        {0x03, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B"},

        //
        // Model B Rev 2.0
        //
        {0x04, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B"},
        {0x05, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B"},
        {0x06, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B"},

        //
        // Model A
        //
        {0x07, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model A"},
        {0x08, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model A"},
        {0x09, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model A"},

        //
        // Model B
        //
        {0x0d, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B"},
        {0x0e, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B"},
        {0x0f, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B"},

        //
        // Model B+
        //
        {0x10, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B+"},
        {0x13, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B+"},
        {0x900032, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model B+"},

        //
        // Compute Module
        //
        {0x11, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Compute Module 1"},
        {0x14, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Compute Module 1"},
        {0xa020a0, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Compute Module 3/L3"},
        {0xa02100, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Compute Module 3+"},
        {0xa220a0, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Compute Module 3 Rev 1.0"},

        //
        // Pi Zero
        //
        {0x900092, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Pi Zero v1.2"},
        {0x900093, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Pi Zero v1.3"},
        {0x920093, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Pi Zero v1.3"},
        {0x9200c1, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Pi Zero W v1.1"},
        {0x9000c1, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Pi Zero W v1.1"},
        //
        // Model Zero 2 W
        //
        {0x902120, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi Zero 2 W v1.0"},

        //
        // Model A+
        //
        {0x12, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model A+"},
        {0x15, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model A+"},
        {0x900021, RPI_HWVER_TYPE_PI1, PERIPH_BASE_RPI, VIDEOCORE_BASE_RPI, "Model A+"},

        //
        // Pi 2 Model B
        //
        {0xa01041, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 2"},
        {0xa01040, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 2"},
        {0xa21041, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 2"},

        //
        // Pi 2 with BCM2837
        //
        {0xa22042, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 2"},

        //
        // Pi 3 Model B
        //
        {0xa020d4, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 3 B+"},
        {0xa020d3, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 3 B+"},
        {0xa32082, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 3 B"},
        {0xa02082, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 3"},
        {0xa02083, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 3"},
        {0xa22082, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 3"},
        {0xa22083, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Pi 3"},
        {0x9020e0, RPI_HWVER_TYPE_PI2, PERIPH_BASE_RPI2, VIDEOCORE_BASE_RPI2, "Model 3 A+"}};

    // 获取 rpi_hw_info 的大小
    constexpr size_t rpi_hw_info_size = sizeof(rpi_hw_info) / sizeof(rpi_hw_info[0]);

    const rpi_hw_t *rpi_hw_detect()
    {
        const rpi_hw_t *result = nullptr;
        uint32_t rev = 0;
        std::ifstream file;
        std::string line;

#ifdef __aarch64__
        file.open("/proc/device-tree/system/linux,revision", std::ios::binary);
        if (!file.is_open())
        {
            return nullptr;
        }

        file.read(reinterpret_cast<char *>(&rev), sizeof(uint32_t));
        if (file.gcount() != sizeof(uint32_t))
        {
            file.close();
            return nullptr;
        }

#if __BYTE_ORDER == __LITTLE_ENDIAN
        rev = __bswap_32(rev); // linux,revision appears to be in big endian
#endif

        for (size_t i = 0; i < rpi_hw_info_size; ++i)
        {
            if (rev == rpi_hw_info[i].hwver)
            {
                result = &rpi_hw_info[i];
                break;
            }
        }
#else
        file.open("/proc/cpuinfo");
        if (!file.is_open())
        {
            return nullptr;
        }

        while (std::getline(file, line))
        {
            if (line.find(HW_VER_STRING) != std::string::npos)
            {
                std::string::size_type pos = line.find(": ");
                if (pos == std::string::npos)
                {
                    continue;
                }

                std::string rev_str = line.substr(pos + 2);
                std::stringstream ss;
                ss << std::hex << rev_str;
                ss >> rev;

                if (ss.fail())
                {
                    continue;
                }

                for (size_t i = 0; i < rpi_hw_info_size; ++i)
                {
                    uint32_t hwver = rpi_hw_info[i].hwver;

                    // Take out warranty and manufacturer bits
                    hwver &= ~(RPI_WARRANTY_MASK | RPI_MANUFACTURER_MASK);
                    rev &= ~(RPI_WARRANTY_MASK | RPI_MANUFACTURER_MASK);

                    if (rev == hwver)
                    {
                        result = &rpi_hw_info[i];
                        break;
                    }
                }
            }
        }
#endif
        file.close();
        return result;
    }

    //
    // Raspberry Pi GPIO Func 树莓派GPIO控制功能
    //

    static inline void gpio_function_set(volatile gpio_t *gpio, uint8_t pin, uint8_t function)
    {
        int regnum = pin / 10;
        int offset = (pin % 10) * 3;
        uint8_t funcmap[] = {4, 5, 6, 7, 3, 2}; // See datasheet for mapping

        if (function > 5)
        {
            return;
        }

        gpio->fsel[regnum] &= ~(0x7 << offset);
        gpio->fsel[regnum] |= ((funcmap[function]) << offset);
    }

    static inline void gpio_level_set(volatile gpio_t *gpio, uint8_t pin, uint8_t level)
    {
        int regnum = pin >> 5;
        int offset = (pin & 0x1f);

        if (level)
        {
            gpio->set[regnum] = (1 << offset);
        }
        else
        {
            gpio->clr[regnum] = (1 << offset);
        }
    }

    static inline void gpio_output_set(volatile gpio_t *gpio, uint8_t pin, uint8_t output)
    {
        int regnum = pin / 10;
        int offset = (pin % 10) * 3;
        uint8_t function = output ? 1 : 0; // See datasheet for mapping

        gpio->fsel[regnum] &= ~(0x7 << offset);
        gpio->fsel[regnum] |= ((function & 0x7) << offset);
    }

    //
    // memory management function 内存管理功能
    //
    void *mapmem(uint32_t base, uint32_t size, const char *mem_dev)
    {
        uint32_t pagemask = ~0UL ^ (getpagesize() - 1);
        uint32_t offsetmask = getpagesize() - 1;
        int mem_fd;
        void *mem;

        mem_fd = open(mem_dev, O_RDWR | O_SYNC);
        if (mem_fd < 0)
        {
            perror("Can't open /dev/mem");
            return NULL;
        }

        mem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, base & pagemask);
        if (mem == MAP_FAILED)
        {
            perror("mmap error\n");
            return NULL;
        }

        close(mem_fd);

        return (char *)mem + (base & offsetmask);
    }

    void *unmapmem(void *addr, uint32_t size)
    {
        uint32_t pagemask = ~0UL ^ (getpagesize() - 1);
        uintptr_t baseaddr = (uintptr_t)addr & pagemask;
        int s;

        s = munmap((void *)baseaddr, size);
        if (s != 0)
        {
            perror("munmap error\n");
        }

        return NULL;
    }

    /*
     * use ioctl to send mbox property message
     */

    static int mbox_property(int file_desc, void *buf)
    {
        int fd = file_desc;
        int ret_val = -1;

        if (fd < 0)
        {
            fd = mbox_open();
        }

        if (fd >= 0)
        {
            ret_val = ioctl(fd, IOCTL_MBOX_PROPERTY, buf);

            if (ret_val < 0)
            {
                perror("ioctl_set_msg failed\n");
            }
        }

        if (file_desc < 0)
        {
            mbox_close(fd);
        }

        return ret_val;
    }

    uint32_t mem_alloc(int file_desc, uint32_t size, uint32_t align, uint32_t flags)
    {
        int i = 0;
        uint32_t p[32];

        p[i++] = 0;          // size
        p[i++] = 0x00000000; // process request

        p[i++] = 0x3000c; // (the tag id)
        p[i++] = 12;      // (size of the buffer)
        p[i++] = 12;      // (size of the data)
        p[i++] = size;    // (num bytes? or pages?)
        p[i++] = align;   // (alignment)
        p[i++] = flags;   // (MEM_FLAG_L1_NONALLOCATING)

        p[i++] = 0x00000000;  // end tag
        p[0] = i * sizeof *p; // actual size

        if (mbox_property(file_desc, p) < 0)
            return 0;
        else
            return p[5];
    }

    uint32_t mem_free(int file_desc, uint32_t handle)
    {
        int i = 0;
        uint32_t p[32];

        p[i++] = 0;          // size
        p[i++] = 0x00000000; // process request

        p[i++] = 0x3000f; // (the tag id)
        p[i++] = 4;       // (size of the buffer)
        p[i++] = 4;       // (size of the data)
        p[i++] = handle;

        p[i++] = 0x00000000;  // end tag
        p[0] = i * sizeof *p; // actual size

        mbox_property(file_desc, p);

        return p[5];
    }

    uint32_t mem_lock(int file_desc, uint32_t handle)
    {
        int i = 0;
        uint32_t p[32];

        p[i++] = 0;          // size
        p[i++] = 0x00000000; // process request

        p[i++] = 0x3000d; // (the tag id)
        p[i++] = 4;       // (size of the buffer)
        p[i++] = 4;       // (size of the data)
        p[i++] = handle;

        p[i++] = 0x00000000;  // end tag
        p[0] = i * sizeof *p; // actual size

        if (mbox_property(file_desc, p) < 0)
            return ~0;
        else
            return p[5];
    }

    uint32_t mem_unlock(int file_desc, uint32_t handle)
    {
        int i = 0;
        uint32_t p[32];

        p[i++] = 0;          // size
        p[i++] = 0x00000000; // process request

        p[i++] = 0x3000e; // (the tag id)
        p[i++] = 4;       // (size of the buffer)
        p[i++] = 4;       // (size of the data)
        p[i++] = handle;

        p[i++] = 0x00000000;   // end tag
        p[0] = i * sizeof(*p); // actual size

        mbox_property(file_desc, p);

        return p[5];
    }

    uint32_t execute_code(int file_desc, uint32_t code, uint32_t r0, uint32_t r1,
                          uint32_t r2, uint32_t r3, uint32_t r4, uint32_t r5)
    {
        int i = 0;
        uint32_t p[32];

        p[i++] = 0;          // size
        p[i++] = 0x00000000; // process request

        p[i++] = 0x30010; // (the tag id)
        p[i++] = 28;      // (size of the buffer)
        p[i++] = 28;      // (size of the data)
        p[i++] = code;
        p[i++] = r0;
        p[i++] = r1;
        p[i++] = r2;
        p[i++] = r3;
        p[i++] = r4;
        p[i++] = r5;

        p[i++] = 0x00000000;   // end tag
        p[0] = i * sizeof(*p); // actual size

        mbox_property(file_desc, p);

        return p[5];
    }

    uint32_t qpu_enable(int file_desc, uint32_t enable)
    {
        int i = 0;
        uint32_t p[32];

        p[i++] = 0;          // size
        p[i++] = 0x00000000; // process request

        p[i++] = 0x30012; // (the tag id)
        p[i++] = 4;       // (size of the buffer)
        p[i++] = 4;       // (size of the data)
        p[i++] = enable;

        p[i++] = 0x00000000;   // end tag
        p[0] = i * sizeof(*p); // actual size

        mbox_property(file_desc, p);

        return p[5];
    }

    uint32_t execute_qpu(int file_desc, uint32_t num_qpus, uint32_t control,
                         uint32_t noflush, uint32_t timeout)
    {
        int i = 0;
        uint32_t p[32];

        p[i++] = 0;          // size
        p[i++] = 0x00000000; // process request
        p[i++] = 0x30011;    // (the tag id)
        p[i++] = 16;         // (size of the buffer)
        p[i++] = 16;         // (size of the data)
        p[i++] = num_qpus;
        p[i++] = control;
        p[i++] = noflush;
        p[i++] = timeout; // ms

        p[i++] = 0x00000000;   // end tag
        p[0] = i * sizeof(*p); // actual size

        mbox_property(file_desc, p);

        return p[5];
    }

    int mbox_open(void)
    {
        int file_desc;
        char filename[64];

        file_desc = open("/dev/vcio", 0);
        if (file_desc >= 0)
        {
            return file_desc;
        }

        // open a char device file used for communicating with kernel mbox driver
        sprintf(filename, "/tmp/mailbox-%d", getpid());
        unlink(filename);
        if (mknod(filename, S_IFCHR | 0600, makedev(100, 0)) < 0)
        {
            perror("Failed to create mailbox device\n");
            return -1;
        }
        file_desc = open(filename, 0);
        if (file_desc < 0)
        {
            perror("Can't open device file\n");
            unlink(filename);
            return -1;
        }
        unlink(filename);

        return file_desc;
    }

    void mbox_close(int file_desc)
    {
        close(file_desc);
    }

    //
    // DMA function DMA管理功能
    //
    // DMA address mapping by DMA number index
    static const uint32_t dma_offset[] = {
        DMA0_OFFSET,
        DMA1_OFFSET,
        DMA2_OFFSET,
        DMA3_OFFSET,
        DMA4_OFFSET,
        DMA5_OFFSET,
        DMA6_OFFSET,
        DMA7_OFFSET,
        DMA8_OFFSET,
        DMA9_OFFSET,
        DMA10_OFFSET,
        DMA11_OFFSET,
        DMA12_OFFSET,
        DMA13_OFFSET,
        DMA14_OFFSET,
        DMA15_OFFSET};

    uint32_t dmanum_to_offset(int dmanum)
    {
        int array_size = sizeof(dma_offset) / sizeof(dma_offset[0]);

        if (dmanum >= array_size)
        {
            return 0;
        }

        return dma_offset[dmanum];
    }
    //
    // PWM function PWM管理功能
    //
    // Mapping of Pin to alternate function for PWM channel 0
    const pwm_pin_table_t pwm_pin_chan0[] = {
        pwm_pin_table_t(12, 0),
        pwm_pin_table_t(18, 5),
        pwm_pin_table_t(40, 0)};

    // Mapping of Pin to alternate function for PWM channel 1
    const pwm_pin_table_t pwm_pin_chan1[] = {
        pwm_pin_table_t(13, 0),
        pwm_pin_table_t(19, 5),
        pwm_pin_table_t(41, 0),
        pwm_pin_table_t(45, 0)};

    const pwm_pin_tables_t pwm_pin_tables[RPI_PWM_CHANNELS] = {
        {pwm_pin_chan0, sizeof(pwm_pin_chan0) / sizeof(pwm_pin_chan0[0])},
        {pwm_pin_chan1, sizeof(pwm_pin_chan1) / sizeof(pwm_pin_chan1[0])}};

    int pwm_pin_alt(int chan, int pinnum)
    {
        const pwm_pin_tables_t &pintable = pwm_pin_tables[chan];

        for (size_t i = 0; i < pintable.count; ++i)
        {
            if (pintable.pins[i].pinnum == pinnum)
            {
                return pintable.pins[i].altnum;
            }
        }
        return -1;
    }

    //
    // WS2812b function ws2812b核心功能
    //


#define BUS_TO_PHYS(x) ((x) & ~0xC0000000)

#define OSC_FREQ 19200000     // crystal frequency
#define OSC_FREQ_PI4 54000000 // Pi 4 crystal frequency

/* 4 colors (R, G, B + W), 8 bits per byte, 3 symbols per bit + 55uS low for reset signal */
#define LED_COLOURS 4
#define LED_RESET_uS 55
#define LED_BIT_COUNT(leds, freq) ((leds * LED_COLOURS * 8 * 3) + ((LED_RESET_uS * \
                                                                    (freq * 3)) /  \
                                                                   1000000))

/* Minimum time to wait for reset to occur in microseconds. */
#define LED_RESET_WAIT_TIME 300

// Pad out to the nearest uint32 + 32-bits for idle low/high times the number of channels
#define PWM_BYTE_COUNT(leds, freq) (((((LED_BIT_COUNT(leds, freq) >> 3) & ~0x7) + 4) + 4) * \
                                    RPI_PWM_CHANNELS)

// Driver mode definitions
#define NONE 0
#define PWM 1

    // We use the mailbox interface to request memory from the VideoCore.
    // This lets us request one physically contiguous chunk, find its
    // physical address, and map it 'uncached' so that writes from this
    // code are immediately visible to the DMA controller.  This struct
    // holds data relevant to the mailbox interface.
    typedef struct videocore_mbox
    {
        int handle;         /* From mbox_open() */
        unsigned mem_ref;   /* From mem_alloc() */
        unsigned bus_addr;  /* From mem_lock() */
        unsigned size;      /* Size of allocation */
        uint8_t *virt_addr; /* From mapmem() */
    } videocore_mbox_t;

    typedef struct ws2811_device
    {
        int driver_mode;
        volatile uint8_t *pxl_raw;
        volatile dma_t *dma;
        volatile pwm_t *pwm;
        volatile dma_cb_t *dma_cb;
        uint32_t dma_cb_addr;
        volatile gpio_t *gpio;
        volatile cm_clk_t *cm_clk;
        videocore_mbox_t mbox;
        int max_count;
    } ws2811_device_t;

    /**
     * Provides monotonic timestamp in microseconds.
     *
     * @returns  Current timestamp in microseconds or 0 on error.
     */
    static uint64_t get_microsecond_timestamp()
    {
        struct timespec t;

        if (clock_gettime(CLOCK_MONOTONIC_RAW, &t) != 0)
        {
            return 0;
        }

        return (uint64_t)t.tv_sec * 1000000 + t.tv_nsec / 1000;
    }

    /**
     * Iterate through the channels and find the largest led count.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  Maximum number of LEDs in all channels.
     */
    static int max_channel_led_count(ws2811_t *ws2811)
    {
        int chan, max = 0;

        for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
        {
            if (ws2811->channel[chan].count > max)
            {
                max = ws2811->channel[chan].count;
            }
        }

        return max;
    }

    /**
     * Map all devices into userspace memory.
     * Not called for SPI
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  0 on success, -1 otherwise.
     */
    static int map_registers(ws2811_t *ws2811)
    {
        ws2811_device_t *device = ws2811->device;
        const rpi_hw_t *rpi_hw = ws2811->rpi_hw;
        uint32_t base = ws2811->rpi_hw->periph_base;
        uint32_t dma_addr;
        uint32_t offset = 0;

        dma_addr = dmanum_to_offset(ws2811->dmanum);
        if (!dma_addr)
        {
            return -1;
        }
        dma_addr += rpi_hw->periph_base;

        device->dma = static_cast<volatile dma_t *>(mapmem(dma_addr, sizeof(dma_t), DEV_MEM));
        if (!device->dma)
        {
            return -1;
        }

        switch (device->driver_mode)
        {
        case PWM:
            device->pwm = static_cast<volatile pwm_t *>(mapmem(PWM_OFFSET + base, sizeof(pwm_t), DEV_MEM));
            if (!device->pwm)
            {
                return -1;
            }
            break;
        }
        /*
         * The below call can potentially work with /dev/gpiomem instead.
         * However, it used /dev/mem before, so I'm leaving it as such.
         */

        device->gpio = static_cast<volatile gpio_t *>(mapmem(GPIO_OFFSET + base, sizeof(gpio_t), DEV_MEM));
        if (!device->gpio)
        {
            return -1;
        }

        switch (device->driver_mode)
        {
        case PWM:
            offset = CM_PWM_OFFSET;
            break;
        }
        device->cm_clk = static_cast<volatile cm_clk_t *>(mapmem(offset + base, sizeof(cm_clk_t), DEV_MEM));
        if (!device->cm_clk)
        {
            return -1;
        }

        return 0;
    }

    /**
     * Unmap all devices from virtual memory.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  None
     */
    static void unmap_registers(ws2811_t *ws2811)
    {
        ws2811_device_t *device = ws2811->device;

        if (device->dma)
        {
            unmapmem((void *)device->dma, sizeof(dma_t));
        }

        if (device->pwm)
        {
            unmapmem((void *)device->pwm, sizeof(pwm_t));
        }

        if (device->cm_clk)
        {
            unmapmem((void *)device->cm_clk, sizeof(cm_clk_t));
        }

        if (device->gpio)
        {
            unmapmem((void *)device->gpio, sizeof(gpio_t));
        }
    }

    /**
     * Given a userspace address pointer, return the matching bus address used by DMA.
     *     Note: The bus address is not the same as the CPU physical address.
     *
     * @param    addr   Userspace virtual address pointer.
     *
     * @returns  Bus address for use by DMA.
     */
    static uint32_t addr_to_bus(ws2811_device_t *device, const volatile void *virt)
    {
        videocore_mbox_t *mbox = &device->mbox;

        uint32_t offset = (uint8_t *)virt - mbox->virt_addr;

        return mbox->bus_addr + offset;
    }

    /**
     * Stop the PWM controller.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  None
     */
    static void stop_pwm(ws2811_t *ws2811)
    {
        ws2811_device_t *device = ws2811->device;
        volatile pwm_t *pwm = device->pwm;
        volatile cm_clk_t *cm_clk = device->cm_clk;

        // Turn off the PWM in case already running
        pwm->ctl = 0;
        usleep(10);

        // Kill the clock if it was already running
        cm_clk->ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_KILL;
        usleep(10);
        while (cm_clk->ctl & CM_CLK_CTL_BUSY)
            ;
    }

    /**
     * Setup the PWM controller in serial mode on both channels using DMA to feed the PWM FIFO.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  None
     */
    static int setup_pwm(ws2811_t *ws2811)
    {
        ws2811_device_t *device = ws2811->device;
        volatile dma_t *dma = device->dma;
        volatile dma_cb_t *dma_cb = device->dma_cb;
        volatile pwm_t *pwm = device->pwm;
        volatile cm_clk_t *cm_clk = device->cm_clk;
        int maxcount = device->max_count;
        uint32_t freq = ws2811->freq;
        int32_t byte_count;

        const rpi_hw_t *rpi_hw = ws2811->rpi_hw;
        const uint32_t rpi_type = rpi_hw->type;
        uint32_t osc_freq = OSC_FREQ;

        if (rpi_type == RPI_HWVER_TYPE_PI4)
        {
            osc_freq = OSC_FREQ_PI4;
        }

        stop_pwm(ws2811);

        // Setup the Clock - Use OSC @ 19.2Mhz w/ 3 clocks/tick
        cm_clk->div = CM_CLK_DIV_PASSWD | CM_CLK_DIV_DIVI(osc_freq / (3 * freq));
        cm_clk->ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_SRC_OSC;
        cm_clk->ctl = CM_CLK_CTL_PASSWD | CM_CLK_CTL_SRC_OSC | CM_CLK_CTL_ENAB;
        usleep(10);
        while (!(cm_clk->ctl & CM_CLK_CTL_BUSY))
            ;

        // Setup the PWM, use delays as the block is rumored to lock up without them.  Make
        // sure to use a high enough priority to avoid any FIFO underruns, especially if
        // the CPU is busy doing lots of memory accesses, or another DMA controller is
        // busy.  The FIFO will clock out data at a much slower rate (2.6Mhz max), so
        // the odds of a DMA priority boost are extremely low.

        pwm->rng1 = 32; // 32-bits per word to serialize
        usleep(10);
        pwm->ctl = RPI_PWM_CTL_CLRF1;
        usleep(10);
        pwm->dmac = RPI_PWM_DMAC_ENAB | RPI_PWM_DMAC_PANIC(7) | RPI_PWM_DMAC_DREQ(3);
        usleep(10);
        pwm->ctl = RPI_PWM_CTL_USEF1 | RPI_PWM_CTL_MODE1 |
                   RPI_PWM_CTL_USEF2 | RPI_PWM_CTL_MODE2;
        if (ws2811->channel[0].invert)
        {
            pwm->ctl |= RPI_PWM_CTL_POLA1;
        }
        if (ws2811->channel[1].invert)
        {
            pwm->ctl |= RPI_PWM_CTL_POLA2;
        }
        usleep(10);
        pwm->ctl |= RPI_PWM_CTL_PWEN1 | RPI_PWM_CTL_PWEN2;

        // Initialize the DMA control block
        byte_count = PWM_BYTE_COUNT(maxcount, freq);
        dma_cb->ti = RPI_DMA_TI_NO_WIDE_BURSTS | // 32-bit transfers
                     RPI_DMA_TI_WAIT_RESP |      // wait for write complete
                     RPI_DMA_TI_DEST_DREQ |      // user peripheral flow control
                     RPI_DMA_TI_PERMAP(5) |      // PWM peripheral
                     RPI_DMA_TI_SRC_INC;         // Increment src addr

        dma_cb->source_ad = addr_to_bus(device, device->pxl_raw);

        dma_cb->dest_ad = (uintptr_t) & ((pwm_t *)PWM_PERIPH_PHYS)->fif1;
        dma_cb->txfr_len = byte_count;
        dma_cb->stride = 0;
        dma_cb->nextconbk = 0;

        dma->cs = 0;
        dma->txfr_len = 0;

        return 0;
    }

    /**
     * Start the DMA feeding the PWM FIFO.  This will stream the entire DMA buffer out of both
     * PWM channels.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  None
     */
    static void dma_start(ws2811_t *ws2811)
    {
        ws2811_device_t *device = ws2811->device;
        volatile dma_t *dma = device->dma;
        uint32_t dma_cb_addr = device->dma_cb_addr;

        dma->cs = RPI_DMA_CS_RESET;
        usleep(10);

        dma->cs = RPI_DMA_CS_INT | RPI_DMA_CS_END;
        usleep(10);

        dma->conblk_ad = dma_cb_addr;
        dma->debug = 7; // clear debug error flags
        dma->cs = RPI_DMA_CS_WAIT_OUTSTANDING_WRITES |
                  RPI_DMA_CS_PANIC_PRIORITY(15) |
                  RPI_DMA_CS_PRIORITY(15) |
                  RPI_DMA_CS_ACTIVE;
    }

    /**
     * Initialize the application selected GPIO pins for PWM operation.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  0 on success, -1 on unsupported pin
     */
    static int gpio_init(ws2811_t *ws2811)
    {
        volatile gpio_t *gpio = ws2811->device->gpio;
        int chan;
        int altnum;

        for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
        {
            int pinnum = ws2811->channel[chan].gpionum;

            if (pinnum)
            {
                switch (ws2811->device->driver_mode)
                {
                case PWM:
                    altnum = pwm_pin_alt(chan, pinnum);
                    break;
                default:
                    altnum = -1;
                }

                if (altnum < 0)
                {
                    return -1;
                }

                gpio_function_set(gpio, pinnum, altnum);
            }
        }

        return 0;
    }

    /**
     * Initialize the PWM DMA buffer with all zeros, inverted operation will be
     * handled by hardware.  The DMA buffer length is assumed to be a word
     * multiple.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  None
     */
    void pwm_raw_init(ws2811_t *ws2811)
    {
        volatile uint32_t *pxl_raw = (uint32_t *)ws2811->device->pxl_raw;
        int maxcount = ws2811->device->max_count;
        int wordcount = (PWM_BYTE_COUNT(maxcount, ws2811->freq) / sizeof(uint32_t)) /
                        RPI_PWM_CHANNELS;
        int chan;

        for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
        {
            int i, wordpos = chan;

            for (i = 0; i < wordcount; i++)
            {
                pxl_raw[wordpos] = 0x0;
                wordpos += 2;
            }
        }
    }

    /**
     * Cleanup previously allocated device memory and buffers.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  None
     */
    void ws2811_cleanup(ws2811_t *ws2811)
    {
        ws2811_device_t *device = ws2811->device;
        int chan;

        for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
        {
            if (ws2811->channel[chan].leds)
            {
                free(ws2811->channel[chan].leds);
            }
            ws2811->channel[chan].leds = NULL;
        }

        if (device->mbox.handle != -1)
        {
            videocore_mbox_t *mbox = &device->mbox;

            unmapmem(mbox->virt_addr, mbox->size);
            mem_unlock(mbox->handle, mbox->mem_ref);
            mem_free(mbox->handle, mbox->mem_ref);
            mbox_close(mbox->handle);

            mbox->handle = -1;
        }

        if (device)
        {
            free(device);
        }
        ws2811->device = NULL;
    }

    static int set_driver_mode(ws2811_t *ws2811, int gpionum)
    {
        int gpionum2;

        if (gpionum == 18 || gpionum == 12)
        {
            ws2811->device->driver_mode = PWM;
            // Check gpio for PWM1 (2nd channel) is OK if used
            gpionum2 = ws2811->channel[1].gpionum;
            if (gpionum2 == 0 || gpionum2 == 13 || gpionum2 == 19)
            {
                return 0;
            }
        }
        else
        {
            fprintf(stderr, "gpionum %d not allowed\n", gpionum);
            return -1;
        }
        // // For PCM and SPI zero the 2nd channel
        // memset(&ws2811->channel[1], 0, sizeof(ws2811_channel_t));

        return 0;
    }

    static int check_hwver_and_gpionum(ws2811_t *ws2811)
    {
        const rpi_hw_t *rpi_hw;
        int hwver, gpionum;
        int gpionums_B1[] = {10, 18, 21};
        int gpionums_B2[] = {10, 18, 31};
        int gpionums_40p[] = {10, 12, 18, 21};
        int i;

        rpi_hw = ws2811->rpi_hw;
        // std::cout << std::hex << "0x"<<rpi_hw->hwver << std::dec << std::endl;
        hwver = rpi_hw->hwver & 0x0000ffff;
        gpionum = ws2811->channel[0].gpionum;
        if (hwver < 0x0004) // Model B Rev 1
        {
            for (i = 0; i < (int)(sizeof(gpionums_B1) / sizeof(gpionums_B1[0])); i++)
            {
                if (gpionums_B1[i] == gpionum)
                {
                    // Set driver mode to PWM
                    return set_driver_mode(ws2811, gpionum);
                }
            }
        }
        else if (hwver >= 0x0004 && hwver <= 0x000f) // Models B Rev2, A
        {
            for (i = 0; i < (int)(sizeof(gpionums_B2) / sizeof(gpionums_B2[0])); i++)
            {
                if (gpionums_B2[i] == gpionum)
                {
                    // Set driver mode to PWM
                    return set_driver_mode(ws2811, gpionum);
                }
            }
        }
        else if (hwver >= 0x010) // Models B+, A+, 2B, 3B, Zero Zero-W
        {
            if ((ws2811->channel[0].count == 0) && (ws2811->channel[1].count > 0))
            {
                // Special case: nothing in channel 0, channel 1 only PWM1 allowed
                // PWM1 only available on 40 pin GPIO interface
                gpionum = ws2811->channel[1].gpionum;
                if ((gpionum == 13) || (gpionum == 19))
                {
                    ws2811->device->driver_mode = PWM;
                    return 0;
                }
                else
                {
                    return -1;
                }
            }
            for (i = 0; i < (int)(sizeof(gpionums_40p) / sizeof(gpionums_40p[0])); i++)
            {
                if (gpionums_40p[i] == gpionum)
                {
                    // Set driver mode to PWM
                    return set_driver_mode(ws2811, gpionum);
                }
            }
        }
        fprintf(stderr, "Gpio %d is illegal for LED channel 0\n", gpionum);
        return -1;
    }

    /*
     *
     * Application API Functions
     *
     */

    /**
     * Allocate and initialize memory, buffers, pages, PWM, DMA, and GPIO.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  0 on success, -1 otherwise.
     */
    ws2811_return_t ws2811::ws2811_init(ws2811_t *ws2811)
    {
        ws2811_device_t *device;
        const rpi_hw_t *rpi_hw;
        int chan;

        ws2811->rpi_hw = rpi_hw_detect();

        if (!ws2811->rpi_hw)
        {
            return WS2811_ERROR_HW_NOT_SUPPORTED;
        }
        rpi_hw = ws2811->rpi_hw;

        ws2811->device = static_cast<ws2811_device *>(malloc(sizeof(*ws2811->device)));
        if (!ws2811->device)
        {
            return WS2811_ERROR_OUT_OF_MEMORY;
        }
        memset(ws2811->device, 0, sizeof(*ws2811->device));
        device = ws2811->device;

        if (check_hwver_and_gpionum(ws2811) < 0)
        {
            return WS2811_ERROR_ILLEGAL_GPIO;
        }

        device->max_count = max_channel_led_count(ws2811);

        // Determine how much physical memory we need for DMA
        switch (device->driver_mode)
        {
        case PWM:
            device->mbox.size = PWM_BYTE_COUNT(device->max_count, ws2811->freq) +
                                sizeof(dma_cb_t);
            break;
        }
        // Round up to page size multiple
        device->mbox.size = (device->mbox.size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);

        device->mbox.handle = mbox_open();
        if (device->mbox.handle == -1)
        {
            return WS2811_ERROR_MAILBOX_DEVICE;
        }

        device->mbox.mem_ref = mem_alloc(device->mbox.handle, device->mbox.size, PAGE_SIZE,
                                         rpi_hw->videocore_base == 0x40000000 ? 0xC : 0x4);
        if (device->mbox.mem_ref == 0)
        {
            return WS2811_ERROR_OUT_OF_MEMORY;
        }

        device->mbox.bus_addr = mem_lock(device->mbox.handle, device->mbox.mem_ref);
        if (device->mbox.bus_addr == (uint32_t)~0UL)
        {
            mem_free(device->mbox.handle, device->mbox.size);
            return WS2811_ERROR_MEM_LOCK;
        }

        device->mbox.virt_addr = static_cast<uint8_t *>(mapmem(BUS_TO_PHYS(device->mbox.bus_addr), device->mbox.size, DEV_MEM));
        if (!device->mbox.virt_addr)
        {
            mem_unlock(device->mbox.handle, device->mbox.mem_ref);
            mem_free(device->mbox.handle, device->mbox.size);

            ws2811_cleanup(ws2811);
            return WS2811_ERROR_MMAP;
        }

        // Initialize all pointers to NULL.  Any non-NULL pointers will be freed on cleanup.
        device->pxl_raw = NULL;
        device->dma_cb = NULL;
        for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
        {
            ws2811->channel[chan].leds = NULL;
        }

        // Allocate the LED buffers
        for (chan = 0; chan < RPI_PWM_CHANNELS; chan++)
        {
            ws2811_channel_t *channel = &ws2811->channel[chan];

            channel->leds = static_cast<ws2811_led_t *>(malloc(sizeof(ws2811_led_t) * channel->count));
            if (!channel->leds)
            {
                ws2811_cleanup(ws2811);
                return WS2811_ERROR_OUT_OF_MEMORY;
            }

            memset(channel->leds, 0, sizeof(ws2811_led_t) * channel->count);

            if (!channel->strip_type)
            {
                channel->strip_type = WS2811_STRIP_RGB;
            }

            channel->wshift = (channel->strip_type >> 24) & 0xff;
            channel->rshift = (channel->strip_type >> 16) & 0xff;
            channel->gshift = (channel->strip_type >> 8) & 0xff;
            channel->bshift = (channel->strip_type >> 0) & 0xff;
        }

        device->dma_cb = (dma_cb_t *)device->mbox.virt_addr;
        device->pxl_raw = (uint8_t *)device->mbox.virt_addr + sizeof(dma_cb_t);

        switch (device->driver_mode)
        {
        case PWM:
            pwm_raw_init(ws2811);
            break;
        }
        memset((dma_cb_t *)device->dma_cb, 0, sizeof(dma_cb_t));

        // Cache the DMA control block bus address
        device->dma_cb_addr = addr_to_bus(device, device->dma_cb);

        // Map the physical registers into userspace
        if (map_registers(ws2811))
        {
            ws2811_cleanup(ws2811);
            return WS2811_ERROR_MAP_REGISTERS;
        }

        // Initialize the GPIO pins
        if (gpio_init(ws2811))
        {
            unmap_registers(ws2811);
            ws2811_cleanup(ws2811);
            return WS2811_ERROR_GPIO_INIT;
        }

        switch (device->driver_mode)
        {
        case PWM:
            // Setup the PWM, clocks, and DMA
            if (setup_pwm(ws2811))
            {
                unmap_registers(ws2811);
                ws2811_cleanup(ws2811);
                return WS2811_ERROR_PWM_SETUP;
            }
            break;
        }

        return WS2811_SUCCESS;
    }

    /**
     * Shut down DMA, PWM, and cleanup memory.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  None
     */
    void ws2811::ws2811_fini(ws2811_t *ws2811)
    {
        ws2811_wait(ws2811);
        switch (ws2811->device->driver_mode)
        {
        case PWM:
            stop_pwm(ws2811);
            break;
        }
        unmap_registers(ws2811);

        ws2811_cleanup(ws2811);
    }

    /**
     * Wait for any executing DMA operation to complete before returning.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  0 on success, -1 on DMA competion error
     */
    ws2811_return_t ws2811::ws2811_wait(ws2811_t *ws2811)
    {
        volatile dma_t *dma = ws2811->device->dma;

        while ((dma->cs & RPI_DMA_CS_ACTIVE) &&
               !(dma->cs & RPI_DMA_CS_ERROR))
        {
            usleep(10);
        }

        if (dma->cs & RPI_DMA_CS_ERROR)
        {
            fprintf(stderr, "DMA Error: %08x\n", dma->debug);
            return WS2811_ERROR_DMA;
        }

        return WS2811_SUCCESS;
    }

    /**
     * Render the DMA buffer from the user supplied LED arrays and start the DMA
     * controller.  This will update all LEDs on both PWM channels.
     *
     * @param    ws2811  ws2811 instance pointer.
     *
     * @returns  None
     */
    ws2811_return_t ws2811::ws2811_render(ws2811_t *ws2811)
    {
        static uint8_t convert_table[3][256] =
            {
                {0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
                 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
                 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93,
                 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
                 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
                 0x93, 0x93, 0x93, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A,
                 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A,
                 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9B, 0x9B, 0x9B, 0x9B,
                 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B,
                 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B,
                 0x9B, 0x9B, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2,
                 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2,
                 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3,
                 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3,
                 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3,
                 0xD3, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA,
                 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA,
                 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB,
                 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB,
                 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB},
                {0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4D,
                 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
                 0x69, 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x49, 0x49, 0x49,
                 0x49, 0x49, 0x49, 0x49, 0x49, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D,
                 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D,
                 0x6D, 0x6D, 0x6D, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4D, 0x4D,
                 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
                 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x49, 0x49, 0x49, 0x49,
                 0x49, 0x49, 0x49, 0x49, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69,
                 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D,
                 0x6D, 0x6D, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4D, 0x4D, 0x4D,
                 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
                 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x49, 0x49, 0x49, 0x49, 0x49,
                 0x49, 0x49, 0x49, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69,
                 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D,
                 0x6D, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4D, 0x4D, 0x4D, 0x4D,
                 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6D,
                 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49,
                 0x49, 0x49, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x69, 0x69, 0x69,
                 0x69, 0x69, 0x69, 0x69, 0x69, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D},
                {0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24,
                 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6,
                 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34,
                 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6,
                 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4,
                 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26,
                 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4,
                 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36,
                 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24,
                 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6,
                 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34,
                 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6,
                 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4,
                 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26,
                 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4,
                 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36,
                 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24,
                 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6,
                 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34,
                 0x36, 0xA4, 0xA6, 0xB4, 0xB6, 0x24, 0x26, 0x34, 0x36, 0xA4, 0xA6, 0xB4, 0xB6}};

        volatile uint8_t *pxl_raw = ws2811->device->pxl_raw;
        int driver_mode = ws2811->device->driver_mode;
        int i, l, chan;
        unsigned j;
        ws2811_return_t ret = WS2811_SUCCESS;
        uint32_t protocol_time = 0;
        static uint64_t previous_timestamp = 0;

        for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) // Channel
        {
            ws2811_channel_t *channel = &ws2811->channel[chan];

            int wordpos = chan; // PWM
            int bytepos = 0;    // SPI
            const int scale = (channel->brightness & 0xff) + 1;
            uint8_t array_size = 3; // Assume 3 color LEDs, RGB

            // If our shift mask includes the highest nibble, then we have 4 LEDs, RBGW.
            if (channel->strip_type & SK6812_SHIFT_WMASK)
            {
                array_size = 4;
            }

            // 1.25µs per bit
            const uint32_t channel_protocol_time = channel->count * array_size * 8 * 1.25;

            // Only using the channel which takes the longest as both run in parallel
            if (channel_protocol_time > protocol_time)
            {
                protocol_time = channel_protocol_time;
            }

            for (i = 0; i < channel->count; i++) // Led
            {
                uint8_t color[] =
                    {
                        static_cast<uint8_t>((((channel->leds[i] >> channel->rshift) & 0xff) * scale) >> 8), // red
                        static_cast<uint8_t>((((channel->leds[i] >> channel->gshift) & 0xff) * scale) >> 8), // green
                        static_cast<uint8_t>((((channel->leds[i] >> channel->bshift) & 0xff) * scale) >> 8), // blue
                        static_cast<uint8_t>((((channel->leds[i] >> channel->wshift) & 0xff) * scale) >> 8), // white
                    };

                for (j = 0; j < array_size; j++) // Color
                {
                    for (l = 0; l < 3; ++l)
                    {
                        uint8_t pos = 3 - bytepos;
                        uint8_t val = convert_table[l][color[j]];
                        if ((driver_mode != PWM) && channel->invert)
                            val = ~val;

                        pxl_raw[wordpos * 4 + pos] = val;
                        if (++bytepos == 4)
                        {
                            bytepos = 0;
                            wordpos += driver_mode == PWM ? 2 : 1;
                        }
                    }
                }
            }
        }

        // Wait for any previous DMA operation to complete.
        if ((ret = ws2811_wait(ws2811)) != WS2811_SUCCESS)
        {
            return ret;
        }

        if (ws2811->render_wait_time != 0)
        {
            const uint64_t current_timestamp = get_microsecond_timestamp();
            uint64_t time_diff = current_timestamp - previous_timestamp;

            if (ws2811->render_wait_time > time_diff)
            {
                usleep(ws2811->render_wait_time - time_diff);
            }
        }

        if (driver_mode == PWM)
        {
            dma_start(ws2811);
        }

        // LED_RESET_WAIT_TIME is added to allow enough time for the reset to occur.
        previous_timestamp = get_microsecond_timestamp();
        ws2811->render_wait_time = protocol_time + LED_RESET_WAIT_TIME;

        return ret;
    }

    const char *ws2811::ws2811_get_return_t_str(const ws2811_return_t state)
    {
        const int index = -state;
        static const char *const ret_state_str[] = {WS2811_RETURN_STATES(WS2811_RETURN_STATES_STRING)};

        if (index < (int)(sizeof(ret_state_str) / sizeof(ret_state_str[0])))
        {
            return ret_state_str[index];
        }

        return "";
    }

} // namespace ws2812b
